﻿using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

namespace BZ.Common
{
    /// <summary>
    /// GUID帮助类
    /// <para>主要方法如下：</para>
    /// <para>1. GenerateRpcrt4() //使用rpcrt4.dll 获取有序guid</para>
    /// <para>2. GenerateSecuential() </para>
    /// <para>3. GenerateComb(SequentialGuidType sequentialGuidType = SequentialGuidType.SequentialAsBinary)</para>
    /// </summary>
    public sealed class GuidHelper
    {
        [DllImport("rpcrt4.dll", SetLastError = true)]
        public static extern int UuidCreateSequential(out Guid guid);
        private const int RPC_S_OK = 0;

        /// <summary>
        /// 使用rpcrt4.dll 获取有序guid
        /// </summary>
        /// <returns></returns>
        public static Guid GenerateRpcrt4()
        {
            Guid guid;
            int result = UuidCreateSequential(out guid);
            if (result == RPC_S_OK)
            {
                byte[] guidBytes = guid.ToByteArray();
                Array.Reverse(guidBytes, 0, 4);
                Array.Reverse(guidBytes, 4, 2);
                Array.Reverse(guidBytes, 6, 2);

                return new Guid(guidBytes);
            }
            else
                return Guid.NewGuid();
        }

        public static Guid GenerateSecuential()
        {
            byte[] uid = Guid.NewGuid().ToByteArray();
            byte[] binDate = BitConverter.GetBytes(DateTime.UtcNow.Ticks);

            byte[] secuentialGuid = new byte[uid.Length];

            secuentialGuid[0] = uid[0];
            secuentialGuid[1] = uid[1];
            secuentialGuid[2] = uid[2];
            secuentialGuid[3] = uid[3];
            secuentialGuid[4] = uid[4];
            secuentialGuid[5] = uid[5];
            secuentialGuid[6] = uid[6];
            // set the first part of the 8th byte to '1100' so     
            // later we'll be able to validate it was generated by us  

            secuentialGuid[7] = (byte)(0xc0 | (0xf & uid[7]));

            // the last 8 bytes are sequential,    
            // it minimizes index fragmentation   
            // to a degree as long as there are not a large    
            // number of Secuential-Guids generated per millisecond 

            secuentialGuid[9] = binDate[0];
            secuentialGuid[8] = binDate[1];
            secuentialGuid[15] = binDate[2];
            secuentialGuid[14] = binDate[3];
            secuentialGuid[13] = binDate[4];
            secuentialGuid[12] = binDate[5];
            secuentialGuid[11] = binDate[6];
            secuentialGuid[10] = binDate[7];

            return new Guid(secuentialGuid);
        }

        /// <summary>
        /// 获取有序的唯一ID。
        /// </summary>
        /// <returns></returns>
        public static Guid GenerateComb(SequentialGuidType sequentialGuidType = SequentialGuidType.SequentialAsBinary)
        {
            return SequentialGuidGenerator.NewSequentialGuid(sequentialGuidType);
        }
        /// <summary>
        /// 根据枚举生成不同的有序GUID
        /// http://www.codeproject.com/Articles/388157/GUIDs-as-fast-primary-keys-under-multiple-database
        /// </summary>
        private static class SequentialGuidGenerator
        {
            private static readonly RNGCryptoServiceProvider Rng = new RNGCryptoServiceProvider();

            public static Guid NewSequentialGuid(SequentialGuidType guidType)
            {
                var randomBytes = new byte[10];
                Rng.GetBytes(randomBytes);

                var timestamp = DateTime.UtcNow.Ticks / 10000L;
                var timestampBytes = BitConverter.GetBytes(timestamp);

                if (BitConverter.IsLittleEndian)
                {
                    Array.Reverse(timestampBytes);
                }

                var guidBytes = new byte[16];

                switch (guidType)
                {
                    case SequentialGuidType.SequentialAsString:
                    case SequentialGuidType.SequentialAsBinary:
                        Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
                        Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);

                        // If formatting as a string, we have to reverse the order
                        // of the Data1 and Data2 blocks on little-endian systems.
                        if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(guidBytes, 0, 4);
                            Array.Reverse(guidBytes, 4, 2);
                        }
                        break;
                    case SequentialGuidType.SequentialAtEnd:
                        Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
                        Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("guidType", guidType, null);
                }

                return new Guid(guidBytes);
            }
        }

        /// <summary>
        /// 生成日期ID
        /// </summary>
        /// <returns></returns>
        public static string GenerateDateID()
        {
            string id = DateTime.Now.ToString("yyyyMMddHHmmssff"); 
            string guid = Guid.NewGuid().ToString().Replace("-", "");
            id += guid.Substring(0, 4);
            return id.ToUpper();
        }
    }

    /// <summary>
    /// 有序GUID的类型
    /// <para>1. MSSQL 	        uniqueidentifier 	SequentialAtEnd </para>
    /// <para>2. MySQL 	        char(36) 	        SequentialAsString </para>
    /// <para>3. Oracle 	    raw(16) 	        SequentialAsBinary </para>
    /// <para>4. PostgreSQL     uuid 	            SequentialAsString </para>
    /// </summary>
    public enum SequentialGuidType
    {
        /// <summary>
        /// MySQL,PostgreSQL
        /// </summary>
        SequentialAsString,
        /// <summary>
        /// Oracle
        /// </summary>
        SequentialAsBinary,
        /// <summary>
        /// MSSQL
        /// </summary>
        SequentialAtEnd
    }
}
